#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import json
import os
import shlex
import shutil
import subprocess
import uuid
from functools import wraps
from io import TextIOBase
from pathlib import Path
from typing import Union, Callable, Any

from common.err_code import CommErrCode
from common.const import RpcToolInterface
from common.parse_parafile import get_env_variable
from saporacle.comm.constants import RPCConstants, SAPOracleConsts, SAPOracleErrorCode
from saporacle.comm.logger import logger
from saporacle.comm.saporacle_exception import SAPOracleException


def logger_watched(level: int) -> Callable:
    """0 <= level <= 4"""
    level = max(0, min(level, 4))

    def decorate(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            ids = [get_env_variable(k) for k in ["pid", "job_id", "sub_job_id"]]
            logger.info(f"{'-' * (5 - level) * 8}> %s start. pid: %s, job_id: %s, sub_job_id: %s", func.__name__, *ids)
            result = func(*args, **kwargs)
            logger.info(f"{'-' * (5 - level) * 8}> %s end. pid: %s, job_id: %s, sub_job_id: %s", func.__name__, *ids)
            return result
        return wrapper

    return decorate


def run_subprocess(command: str, inp: str = None,
                   stdout: Union[int, TextIOBase] = subprocess.PIPE,
                   stderr: Union[int, TextIOBase] = subprocess.PIPE,
                   timeout: int = None
                   ) -> subprocess.CompletedProcess:
    """
    :raise SAPOracleException:
    """
    try:
        process = subprocess.run(shlex.split(command, posix=os.name == "posix"), input=inp,
                                 stdout=stdout, stderr=stderr, text=True, timeout=timeout)
    except subprocess.TimeoutExpired:
        logger.error(f"Timeout exception occurred while run subprocess. {command}")
        raise SAPOracleException(CommErrCode.FAILED_EXECUTE_COMMAND, command, "Timeout")
    except Exception as e:
        logger.error(f"Exception occurred while running subprocess. {command} {e}", exc_info=1)
        raise SAPOracleException(CommErrCode.FAILED_EXECUTE_COMMAND, command, "UnknownException")
    if process.stderr:
        logger.error(f"Error output by running subprocess. {command} {process.stderr}")
        raise SAPOracleException(CommErrCode.FAILED_EXECUTE_COMMAND, command, process.stderr)
    return process


def rpctool_api(pid: str, api: RpcToolInterface, param: dict) -> dict:
    """
    :raise SAPOracleException:
    """
    param_file = Path(SAPOracleConsts.TMP_PATH) / f"{RPCConstants.PARAM_FILE_PREFIX}{pid}_{uuid.uuid4()}"
    result_file = Path(SAPOracleConsts.STMP_PATH) / f"{RPCConstants.RESULT_FILE_PREFIX}{pid}_{uuid.uuid4()}"
    param_file.write_text(json.dumps(param), encoding="utf-8")
    command = f"{RPCConstants.RPC_TOOL_PATH} {api} {param_file} {result_file}"
    try:
        run_subprocess(command)
    except SAPOracleException as e:
        logger.error("RPCtool failed.")
        raise SAPOracleException(SAPOracleErrorCode.UNKNOWN_ERROR)
    finally:
        param_file.exists() and param_file.unlink()
    if not result_file.exists():
        logger.warn(f"Rpctool result file not generated. Return empty.")
        return {}
    result = json.loads(result_file.read_text(encoding="utf-8"))
    result_file.unlink()
    return result


def remove_anypath(anypath: Union[str, Path]) -> None:
    """
    :param anypath: link, file or dir
    :raise SAPOracleException:
    """
    if not os.path.exists(anypath) and not os.path.islink(anypath):
        logger.info(f"Removing nonexistent path, pass. {anypath}")
        return
    try:
        if os.path.islink(anypath) or os.path.isfile(anypath):
            os.remove(anypath)
        elif os.path.isdir(anypath):
            shutil.rmtree(anypath)
    except Exception as e:
        logger.error(f"Exception occurred while removing path. {anypath} {e}", exc_info=1)
        raise SAPOracleException(SAPOracleErrorCode.UNKNOWN_ERROR)
    logger.info(f"Removed. {anypath}")


def copy_anypath(src: Union[str, Path], dst: Union[str, Path]) -> None:
    """
    Accept input (file, file), (file, dir), (dir, dir)\n
    Dst exist ok, will overwrite.\n
    Will makedirs.
    :raise SAPOracleException:
    """
    try:
        if os.path.isfile(src):
            shutil.copy2(src, dst)
        elif os.path.isdir(src):
            shutil.copytree(src, dst, dirs_exist_ok=True)
        else:
            logger.error(f"Invalid input path.")
            raise SAPOracleException(SAPOracleErrorCode.UNKNOWN_ERROR)
    except Exception as e:
        logger.error(f"Exception occurred while copying. {src} -> {dst}. {e}", exc_info=1)
        raise SAPOracleException(SAPOracleErrorCode.UNKNOWN_ERROR)
    logger.info(f"Copied. {src} -> {dst}.")

